<template>
  <div class="graph-container">
    <div v-if="!graphWrapper" class="loading">容器初始化中...</div>
    <div class="graph-wrapper" ref="graphWrapper"></div>
    <div v-if="loading" class="loading">加载中...</div>
    <div v-if="error" class="error">{{ error }}</div>
    <div v-if="copySuccess" class="copy-tooltip">✓ 已复制节点名称</div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, watch, nextTick } from 'vue';
import * as d3 from 'd3';
import { Link as ApiLink, RelationshipApi } from '@/api/member/relationship';

export interface Node extends d3.SimulationNodeDatum {
  id: string;
  name: string;
  group: number;
  size?: number;
  layer?: number;
  isLeaf?: boolean;
  leafIndex?: number;
  leafCount?: number;
}

interface D3Link extends Omit<ApiLink, 'source' | 'target'> {
  source: Node;
  target: Node;
}

interface GraphApiResponse {
  nodes: { id: string; name: string; group: number; size?: number }[];
  links: ApiLink[];
}

defineOptions({ name: 'RelationshipGraph' })

const graphWrapper = ref<HTMLDivElement | null>(null);
const loading = ref(true);
const error = ref('');
const graphData = ref<{ nodes: Node[]; links: D3Link[] }>({ nodes: [], links: [] });
const copySuccess = ref(false);
let simulation: d3.Simulation<Node, D3Link> | null = null;

const { personId } = defineProps({
  personId: {
    type: [String, Array], // 同时支持字符串和数组类型
    required: true
  }
})

const copyNodeName = (name: string) => {
  navigator.clipboard.writeText(name).then(() => {
    copySuccess.value = true;
    setTimeout(() => copySuccess.value = false, 2000);
  }).catch(err => console.error('复制失败:', err));
};

const fetchGraphData = async () => {
  try {
    loading.value = true;
    error.value = '';
    const response = await RelationshipApi.getGraph(personId) as GraphApiResponse;
    if (!response || !response.nodes || !response.links) throw new Error('API返回格式不正确');
    return processApiData(response);
  } catch (err) {
    const errorMsg = err instanceof Error ? err.message : '未知错误';
    error.value = `获取数据失败: ${errorMsg}`;
  } finally {
    loading.value = false;
  }
};

const processApiData = (data: GraphApiResponse) => {
  const nodes = data.nodes.map(node => ({
    ...node, x: 0, y: 0, layer: node.group, isLeaf: false, leafIndex: -1, leafCount: 0
  })) as Node[];

  const sourceNodeIds = new Set(data.links.map(link => link.source));
  nodes.forEach(node => node.isLeaf = !sourceNodeIds.has(node.id));

  const layerLeafMap = new Map<number, Node[]>();
  nodes.forEach(node => {
    if (node.isLeaf && node.layer !== undefined) {
      if (!layerLeafMap.has(node.layer)) layerLeafMap.set(node.layer, []);
      layerLeafMap.get(node.layer)!.push(node);
    }
  });

  layerLeafMap.forEach((leafNodes) => {
    leafNodes.sort((a, b) => a.name.localeCompare(b.name));
    const leafCount = leafNodes.length;
    leafNodes.forEach((node, index) => {
      node.leafIndex = index;
      node.leafCount = leafCount;
    });
  });

  const links = data.links.map(link => {
    const sourceNode = nodes.find(n => n.id === link.source);
    const targetNode = nodes.find(n => n.id === link.target);
    return sourceNode && targetNode ? { ...link, source: sourceNode, target: targetNode } : null;
  }).filter(Boolean) as D3Link[];

  graphData.value = { nodes, links };
  return graphData.value;
};

const initGraph = () => {
  if (!graphWrapper.value) return;
  const container = graphWrapper.value;
  container.style.width = '100%';
  container.style.height = '600px';
  const width = container.clientWidth;
  const height = container.clientHeight;
  const centerX = width / 2;

  if (width === 0 || height === 0) return;

  d3.select(container).selectAll('*').remove();

  const svg = d3.select(container).append('svg').attr('width', width).attr('height', height);

  svg.append('defs').selectAll('marker')
    .data(['end'])
    .enter().append('marker')
    .attr('id', d => d)
    .attr('viewBox', '0 -5 10 10')
    .attr('refX', 25)
    .attr('refY', 0)
    .attr('markerWidth', 6)
    .attr('markerHeight', 6)
    .attr('orient', 'auto')
    .append('path')
    .attr('d', 'M0,-5L10,0L0,5')
    .attr('fill', '#999');

  const layerCount = 3;
  const layerSpacing = height / (layerCount + 1);
  const nodeSpacing = 120; // 1. 调大节点水平间距（原80px → 120px）

  simulation = d3.forceSimulation<Node, D3Link>(graphData.value.nodes)
    .force('link', d3.forceLink<Node, D3Link>(graphData.value.links)
      .id(d => d.id)
      .distance(d => d.source.isLeaf || d.target.isLeaf ? 100 : 150) // 2. 调大连线距离（可选）
    )
    .force('charge', d3.forceManyBody<Node>().strength(d => d.isLeaf ? -400 : -700)) // 增强排斥力避免拥挤
    .force('center', d3.forceCenter(centerX, height / 2))
    .force('collision', d3.forceCollide<Node>()
      .radius(d => (d.size || 10) + 40) // 3. 调大碰撞半径（原30 → 40）
    )
    .force('y', d3.forceY<Node>(d => d.layer! * layerSpacing).strength(0.9))
    .force('x', d3.forceX<Node>(d => {
      if (d.isLeaf && d.leafIndex !== undefined && d.leafCount) {
        const leafCount = d.leafCount;
        const index = d.leafIndex;
        if (leafCount % 2 === 1) {
          const mid = Math.floor(leafCount / 2);
          return centerX + (index - mid) * nodeSpacing; // 使用调大的nodeSpacing
        } else {
          const half = leafCount / 2;
          return centerX + (index - half + 0.5) * nodeSpacing;
        }
      }
      return centerX;
    }).strength(d => d.isLeaf ? 0.8 : 0.1))

  const link = svg.append('g')
    .attr('class', 'links')
    .selectAll('line')
    .data(graphData.value.links)
    .enter().append('line')
    .attr('class', 'link')
    .attr('stroke', '#999')
    .attr('stroke-width', d => Math.sqrt(d.value))
    .attr('marker-end', 'url(#end)');

  const node = svg.append('g')
    .attr('class', 'nodes')
    .selectAll('.node')
    .data(graphData.value.nodes)
    .enter().append('g')
    .attr('class', 'node')
    .call(d3.drag<SVGGElement, Node>()
      .on('start', dragstarted)
      .on('drag', dragged)
      .on('end', dragended))
    .on('click', (event, d) => { event.stopPropagation(); copyNodeName(d.name); })
    .on('mouseover', (event) => {
      d3.select(event.currentTarget).select('circle').attr('stroke', '#333').attr('stroke-width', 3);
    })
    .on('mouseout', (event) => {
      d3.select(event.currentTarget).select('circle').attr('stroke', '#fff').attr('stroke-width', 2);
    });

  node.append('circle')
    .attr('r', d => d.size || 10)
    .attr('fill', d => {
      const colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd'];
      return d.isLeaf ? '#2ca02c' : colors[d.group % colors.length];
    })
    .attr('stroke', '#fff')
    .attr('stroke-width', 2)
    .attr('cursor', 'pointer');

  node.append('text')
    .attr('dx', d => (d.size || 10) + 5)
    .attr('dy', '.35em')
    .text(d => d.name)
    .attr('fill', '#333')
    .attr('cursor', 'pointer');

  const tooltip = d3.select('body')
    .append('div')
    .attr('class', 'graph-tooltip')
    .style('opacity', 0);

  node.on('mouseover', (event, d) => {
    tooltip.transition().duration(200).style('opacity', .9);
    tooltip.html(`<strong>${d.name}</strong><br>组: ${d.group}<br>类型: ${d.isLeaf ? '叶子节点' : '普通节点'}`)
      .style('left', (event.pageX + 10) + 'px')
      .style('top', (event.pageY - 28) + 'px');
  }).on('mouseout', () => {
    tooltip.transition().duration(500).style('opacity', 0);
  });

  const layersG = svg.append('g').attr('class', 'layers');
  for (let i = 1; i <= layerCount; i++) {
    layersG.append('line')
      .attr('x1', 0)
      .attr('y1', i * layerSpacing)
      .attr('x2', width)
      .attr('y2', i * layerSpacing)
      .attr('stroke', '#eee')
      .attr('stroke-dasharray', '5,5');
  }

  simulation.on('tick', () => {
    graphData.value.nodes.forEach(d => {
      if (d.x !== undefined && d.y !== undefined) {
        d.x = Math.max((d.size || 10), Math.min(width - (d.size || 10), d.x));
        d.y = Math.max((d.size || 10), Math.min(height - (d.size || 10), d.y));
      }
    });

    link
      .attr('x1', d => d.source.x!)
      .attr('y1', d => d.source.y!)
      .attr('x2', d => d.target.x!)
      .attr('y2', d => d.target.y!);

    node.attr('transform', d => `translate(${d.x},${d.y})`);
  });

  function dragstarted(event: d3.D3DragEvent<SVGGElement, Node, Node>) {
    if (!event.active && simulation) simulation.alphaTarget(0.3).restart();
    event.subject.fx = event.subject.x;
    event.subject.fy = event.subject.y;
  }

  function dragged(event: d3.D3DragEvent<SVGGElement, Node, Node>) {
    event.subject.fx = event.x;
    if (event.subject.isLeaf) event.subject.fy = event.subject.layer! * layerSpacing;
    else event.subject.fy = event.y;
  }

  function dragended(event: d3.D3DragEvent<SVGGElement, Node, Node>) {
    if (!event.active && simulation) simulation.alphaTarget(0);
    event.subject.fx = null;
    event.subject.fy = null;
  }

  const handleResize = () => {
    if (!simulation) return;
    const newWidth = container.clientWidth;
    const newHeight = container.clientHeight;
    svg.attr('width', newWidth).attr('height', newHeight);
    simulation.force('center', d3.forceCenter(newWidth / 2, newHeight / 2));
    simulation.alpha(0.3).restart();
  };

  window.addEventListener('resize', handleResize);
  return () => window.removeEventListener('resize', handleResize);
};

onMounted(async () => {
  await nextTick();
  await fetchGraphData();
  await nextTick();
  initGraph();
});

watch(graphData, async (newData) => {
  if (newData.nodes.length === 0) return;
  await nextTick();
  initGraph();
});
</script>

<style scoped>
/* 样式保持不变 */
.graph-container {
  width: 100%;
  height: 100%;
  min-height: 600px;
  padding: 20px;
  box-sizing: border-box;
  position: relative;
}

.graph-wrapper {
  width: 100%;
  height: 600px;
  background-color: white;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  overflow: hidden;
}

.loading, .error {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 16px;
}

.error {
  color: #d62728;
}

.copy-tooltip {
  position: absolute;
  top: 20px;
  right: 20px;
  padding: 8px 16px;
  background-color: #2ca02c;
  color: white;
  border-radius: 4px;
  font-size: 14px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
  z-index: 100;
  animation: fadeInOut 2s ease-in-out;
}

@keyframes fadeInOut {
  0% { opacity: 0; transform: translateY(-10px); }
  20% { opacity: 1; transform: translateY(0); }
  80% { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-10px); }
}

:deep(.node) { cursor: pointer; }
:deep(.node circle) { transition: all 0.3s ease; }
:deep(.node:hover circle) { stroke: #333; stroke-width: 3px; }
:deep(.node text) { font-size: 12px; }
:deep(.link) { stroke: #999; stroke-opacity: 0.6; }
:deep(.graph-tooltip) {
  position: absolute;
  padding: 10px;
  background-color: rgba(0, 0, 0, 0.8);
  color: white;
  border-radius: 4px;
  pointer-events: none;
  font-size: 12px;
  transition: opacity 0.3s;
}
:deep(.layers line) { pointer-events: none; }
</style>
